package simulation;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Random;

import streaming.StreamingConfig;

// Basically this is an event-based simulator...
// At every iteration, the simulator dequeues an event and handles it..
// This event could be either be a message delivered at a node or an
// actual event that periodically notifies the node about something: 
//For example, a hearbeat event will periodically inform the node to send heartbeats

public class Simulator {

	private static Simulator _instance = new Simulator();
	public Random random;
	public PriorityQueue queue;
	double time;
	int previousSecond = 0;
	Integer intMessagesSimpleSimulation;
	Integer intDiagnosticRequestMessages;
	Integer intDiagnosticResponseMessages;
	Integer intOriginalChunksCounter;
	Integer intModifiedChunksCounter;
	Integer intTotalChunksSent;
	Integer intTotalChunksReceived;
	Integer intTotalUSetsSentTracker;
	public int chunksSentWithoutCMPerSecond[] = new int [(int)(Config.TIME + StreamingConfig.MCASTWINDOWSIZE) + 1];
	public int chunksSentByCMPerSecond[] = new int [(int)(Config.TIME + StreamingConfig.MCASTWINDOWSIZE) + 1];
	public int uSetsSentPerSecond[] = new int [(int)(Config.TIME + StreamingConfig.MCASTWINDOWSIZE) + 1];
	
	HashMap <Integer, Map<Integer, Integer>> pollutedPeers; // K - chunk id, V -> map of peers polluted
	
	private Simulator() {
		queue = new PriorityQueue();
		random = new Random();
		time = 0;
		this.setIntMessagesSimpleSimulation(new Integer(0));
		this.setIntDiagnosticRequestMessages(new Integer(0));
		this.setIntDiagnosticResponseMessages(new Integer(0));
		this.setIntOriginalChunksCounter(new Integer(0));
		this.setIntModifiedChunksCounter(new Integer(0));
		this.setIntTotalChunksReceived(new Integer(0));
		this.setIntTotalChunksSent(new Integer(0));
		this.setIntTotalUSetsSentTracker(new Integer(0));
		this.pollutedPeers = new HashMap<Integer, Map<Integer,Integer>>();
//		this.singlePollutedPeers = new HashMap<Integer, Integer>();
	}

	public void reset() {
		queue = new PriorityQueue();
		random = new Random();
		time = 0;
		this.setIntMessagesSimpleSimulation(new Integer(0));
		this.setIntDiagnosticRequestMessages(new Integer(0));
		this.setIntDiagnosticResponseMessages(new Integer(0));
		this.setIntOriginalChunksCounter(new Integer(0));
		this.setIntModifiedChunksCounter(new Integer(0));
		this.setIntTotalChunksReceived(new Integer(0));
		this.setIntTotalChunksSent(new Integer(0));
		this.setIntTotalUSetsSentTracker(new Integer(0));
		this.pollutedPeers = new HashMap<Integer, Map<Integer,Integer>>();
	}
	
	public static Simulator getInstance() {
		return _instance;
	}

	public double getTime() {
		return time;
	}

	public Integer getTimeTrunc() {
		return (int) Math.floor(time);
	}
	
	public String time() {
		long msecs = (long) (time * 1000 + 0.5);

		return "" + msecs + ": ";
	}

	public String time(String name) {
		long msecs = (long) (time * 1000 + 0.5);

		return "" + msecs + ": " + name + ": ";
	}

	public void addEvent(double time, EventHandler handler) {
		Event ev = new Event(time, handler);
		if (this.time > time)
			System.out
					.println("FATAL LOGICAL ERROR... TO SCHEDULE AT AN ELAPSED INSTANT!!");
		queue.add(ev);
	}

	Event firstEvent() {
		return (Event) queue.poll();
	}

	// Loops through tasks
	public void run() {
		for (;;) {
			Event ev = firstEvent();

			if (ev == null) {
				return;
			}
			
			time = ev.time;
			
			/* If maximum time is arrived, ends the simulation */
			if (time >= Config.TIME + (10 * StreamingConfig.MCASTWINDOWSIZE)){ // Added '1.1 * StreamingConfig.MCASTWINDOWSIZE' to make possible all data to be transmited to all nodes by the end of simulation
				return;
			}
			// System.out.println("ev???"+ev.handler) ;
			ev.handler.run(ev.time);
		}
	}
	
	public void addIntMessagesSimplesSimulation(){
		this.setIntMessagesSimpleSimulation(this.getIntMessagesSimpleSimulation() + 1);
	}
	
	public void addIntDiagnosticRequestMessages(){
		this.setIntDiagnosticRequestMessages(this.getIntDiagnosticRequestMessages() + 1);
	}
	
	public void addIntDiagnosticResponseMessages(){
		this.setIntDiagnosticResponseMessages(this.getIntDiagnosticResponseMessages() + 1);
	}
	
	public void addIntOriginalChunksCounter(){
		this.setIntOriginalChunksCounter(this.getIntOriginalChunksCounter() + 1);
	}
	
	public void addIntModifiedChunksCounter(){
		this.setIntModifiedChunksCounter(this.getIntModifiedChunksCounter() + 1);
	}
	
	public void addIntTotalChunksReceived(){
		this.setIntTotalChunksReceived(this.getIntTotalChunksReceived() + 1);
	}
	
	public void addIntTotalChunksSent(){
		this.setIntTotalChunksSent(this.getIntTotalChunksSent() + 1);
	}
	
	public void addIntTotalUSetsSentTracker() {
		this.setIntTotalUSetsSentTracker(this.getIntTotalUSetsSentTracker() + 1);
	}
	
	public Integer getIntMessagesSimpleSimulation() {
		return intMessagesSimpleSimulation;
	}

	public void setIntMessagesSimpleSimulation(Integer intMessagesSimpleSimulation) {
		this.intMessagesSimpleSimulation = intMessagesSimpleSimulation;
	}

	public Integer getIntDiagnosticRequestMessages() {
		return intDiagnosticRequestMessages;
	}

	public void setIntDiagnosticRequestMessages(Integer intDiagnosticRequestMessages) {
		this.intDiagnosticRequestMessages = intDiagnosticRequestMessages;
	}

	public Integer getIntDiagnosticResponseMessages() {
		return intDiagnosticResponseMessages;
	}

	public void setIntDiagnosticResponseMessages(
			Integer intDiagnosticResponseMessages) {
		this.intDiagnosticResponseMessages = intDiagnosticResponseMessages;
	}

	public Integer getIntOriginalChunksCounter() {
		return intOriginalChunksCounter;
	}

	public void setIntOriginalChunksCounter(Integer intOriginalChunksCounter) {
		this.intOriginalChunksCounter = intOriginalChunksCounter;
	}

	public Integer getIntModifiedChunksCounter() {
		return intModifiedChunksCounter;
	}

	public void setIntModifiedChunksCounter(Integer intModifiedChunksCounter) {
		this.intModifiedChunksCounter = intModifiedChunksCounter;
	}

	public Integer getIntTotalChunksSent() {
		return intTotalChunksSent;
	}

	public void setIntTotalChunksSent(Integer intTotalChunksSent) {
		this.intTotalChunksSent = intTotalChunksSent;
	}

	public Integer getIntTotalChunksReceived() {
		return intTotalChunksReceived;
	}

	public void setIntTotalChunksReceived(Integer intTotalChunksReceived) {
		this.intTotalChunksReceived = intTotalChunksReceived;
	}

	public HashMap<Integer, Map<Integer, Integer>> getPollutedPeers() {
		return pollutedPeers;
	}

	public void setPollutedPeers(
			HashMap<Integer, Map<Integer, Integer>> pollutedPeers) {
		this.pollutedPeers = pollutedPeers;
	}

	public Integer getIntTotalUSetsSentTracker() {
		return intTotalUSetsSentTracker;
	}

	public void setIntTotalUSetsSentTracker(Integer intTotalUSetsSentTracker) {
		this.intTotalUSetsSentTracker = intTotalUSetsSentTracker;
	}
}